﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IsmsExplorer.Infrastructure.RepositoryFramework;
using IsmsExplorer.Infrastructure.DomainBase;
using Microsoft.Practices.EnterpriseLibrary.Data;
using IsmsExplorer.Infrastructure.EntityFactoryFramework;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using System.Data;
using System.Data.Common;
using IsmsExplorer.Infrastructure.Helper;

namespace IsmsExplorer.Infrastructure.Repositories {
    public abstract class SqlServerRepositoryBase<T> : RepositoryBase<T> where T : EntityBase {
        #region Delegate used to fetch child entities for current aggregate

        public delegate void GetChildDataCallback(T rootEntity, object childPKValue);
        
        #endregion

        #region Data members
        
        Database _db;
        IEntityFactory<T> _entityFactory;
        // For each child entity, map it's primary key to asset delegate used 
        // as asset callback to load the child entity data into the aggregate.
        Dictionary<string, GetChildDataCallback> _childCallbacks;
        protected string _baseQuery;
        protected string _baseWhereClause;

        #endregion

        public SqlServerRepositoryBase() : this(null) {
        }

        public SqlServerRepositoryBase(IUnitOfWork unitOfWork) : base(unitOfWork) {
            _db = InitialiseDatabase();
            _entityFactory = EntityFactoryBuilder<T>.BuildFactory<T>();
            _childCallbacks = new Dictionary<string, GetChildDataCallback>();
            this.InitialiseChildCallbacks();
            _baseQuery = this.GetBaseQuery();
            _baseWhereClause = this.GetBaseWhereClause();
        }

        /// <summary>
        /// Used to initialised the database object. May want to change the initialisation
        /// to using the Unity dependency injection (or even the old facade factory methods)
        /// for compatibility. Currently using the Unity Service Locator.
        /// </summary>
        protected virtual Database InitialiseDatabase() {
            // No argument call uses the default database.
            // Might want to configure asset named instance.
            return EnterpriseLibraryContainer.Current.GetInstance<Database>();
        }

        #region Abstract methods

        protected abstract void InitialiseChildCallbacks();

        protected abstract string GetBaseQuery();
        protected abstract string GetBaseWhereClause();
        protected abstract override void PersistDeletedItem(T item);
        protected abstract override void PersistNewItem(T item);
        protected abstract override void PersistUpdatedItem(T item);

        #endregion

        protected Database Database {
            get { return this._db; }
        }

        protected Dictionary<string, GetChildDataCallback> ChildCallbacks {
            get { return this._childCallbacks; }
        }

        public override IList<T> FindAll() {
            StringBuilder sb = new StringBuilder(GetBaseQuery());
            sb.Append(";");
            return this.BuildEntitiesFromSql(sb.ToString());
        }

        public override T FindBy(object id) {
            StringBuilder sb = new StringBuilder(this.GetBaseQuery());
            sb.Append(" ").Append(this.BuildBaseWhereClause(id)).Append(";");
            return this.BuildEntityFromSql(sb.ToString());
        }

        protected virtual string BuildBaseWhereClause(object id) {
            return string.Format(_baseWhereClause, DataHelper.GetSqlValue(id));
        }

        protected IDataReader ExecuteReader(string sql) {
            DbCommand command = _db.GetSqlStringCommand(sql);
            return _db.ExecuteReader(command);
        }

        protected virtual T BuildEntityFromReader(IDataReader reader) {
            T entity = _entityFactory.BuildEntity(reader);
            if (_childCallbacks != null && _childCallbacks.Count > 0) {
                object childKeyValue = null;
                DataTable columnData = reader.GetSchemaTable();
                foreach (string childKeyName in _childCallbacks.Keys) {
                    if (DataHelper.ReaderContainsColumnName(columnData, childKeyName)) {
                        childKeyValue = reader[childKeyName];
                    } else {
                        childKeyValue = null;
                    }
                    _childCallbacks[childKeyName](entity, childKeyValue);
                }
            }
            return entity;
        }

        protected virtual T BuildEntityFromSql(string sql) {
            T entity = default(T);
            using (IDataReader reader = this.ExecuteReader(sql)) {
                if (reader.Read()) {
                    entity = this.BuildEntityFromReader(reader);
                }
            }
            return entity;
        }

        protected virtual IList<T> BuildEntitiesFromSql(string sql) {
            List<T> entities = new List<T>();
            using (IDataReader reader = this.ExecuteReader(sql)) {
                while (reader.Read()) {
                    entities.Add(this.BuildEntityFromReader(reader));
                }
            }
            return entities;
        }

    } // end public class SqlServerRepositoryBase<T> : RepositoryBase<T>
}
